package Metadata;

import index.BTFile;
import index.FloatKey;
import index.IntegerKey;
import index.KeyClass;
import index.StringKey;
import Common.GoggleDB;
import Common.IndexType;
import Common.Types;
import Transaction.Transaction;
import heap.Heapfile;
import heap.RID;
import heap.Scan;
import heap.Tuple;
import exceptions.*;


public class IndexMgr extends Heapfile {

	public IndexMgr(String filename, Transaction tx) throws Exception {
		super(filename, tx);

		tuple = new Tuple(Tuple.maxSize);
		attrs = new Types[8];

		attrs[0] = new Types(Types.CHAR);
		attrs[1] = new Types(Types.CHAR);
		attrs[2] = new Types(Types.INT);
		attrs[3] = new Types(Types.INT); // 0 = None
										 // 1 = B_Index
										 // 2 = Linear Hash

		attrs[4] = new Types(Types.INT);
		                                 // 0 = Ascending
		                                 // 1 = Descending
		                                 // 2 = Random
		attrs[5] = new Types(Types.INT);
		attrs[6] = new Types(Types.INT);
		                                 //IndexName
		attrs[7] = new Types(Types.CHAR);

		strSizes = new short[3];
		strSizes[0] = (short) MAXNAME;
		strSizes[1] = (short) MAXNAME;
		strSizes[2] = (short) MAXNAME;

		createTuple();
	}

	void createTuple() throws Exception {
		tuple = new Tuple(Tuple.maxSize);
		initTuple();
	}

	void initTuple() throws Exception {
		tuple.setHdr((short) 8, attrs, strSizes);
	}

	// GET ALL INDEXES FOR A RELATION
	// Return indexCnt.
	public IndexSchema[] getRelInfo(String relation, int indexCnt,Transaction tx)
			throws Exception {
		tableSchema record = null;
		int status;
		int recSize;
		RID rid = null;
		Scan pscan = null;
		int count = 0;

		rid = new RID();
		record = new tableSchema();

		GoggleDB.tableMgr.getInfo(relation, record,tx);

		// SET INDEX COUNT BY REFERENCE

		indexCnt = record.indexCnt;
		//System.out.println("indexCnt:"+indexCnt);
		if (indexCnt == 0)
			return null;

		// OPEN SCAN

		pscan = new Scan(this,tx);

		// ALLOCATE INDEX ARRAY

		IndexSchema[] indexes = new IndexSchema[indexCnt];
		for(int i=0;i<indexCnt;i++) {
			indexes[i] = new IndexSchema();
		}
		if (indexes == null) {
			pscan.closeScan();
			throw new DBException(null, "Catalog: No Enough Memory!");
		}

		// SCAN THE FILE

		while (true) {

			tuple = pscan.getNext(rid,tx,relation);
			if (tuple == null)
				throw new DBException(null, "Catalog: Index not Found!");
			initTuple();
			readTuple(tuple, indexes[count],tx);

			if (indexes[count].relName.equalsIgnoreCase(relation) == true)
				count++;

			if (count == indexCnt) // IF ALL INDEXES FOUND
				break;
		}

		pscan.closeScan();

		return indexes;

	}
	
	public boolean getaInfo(String relation,String attrName,IndexSchema record,Transaction tx) throws Exception {
		int recSize;
		RID rid = null;
		Scan pscan = null;

		rid = new RID();

		// OPEN SCAN

		pscan = new Scan(this,tx);

		// SCAN FILE

		while (true) {
			tuple = pscan.getNext(rid,tx,relation);
			if (tuple == null)
				return false;
			initTuple();
			readTuple(tuple, record,tx);

			if (record.attrName.equalsIgnoreCase(attrName)==true)
				break; // FOUND
		}

		pscan.closeScan();

		return true;
	}

	// RETURN INFO ON AN INDEX
	public boolean getInfo(String relation, String IndexName, IndexSchema record,Transaction tx) throws Exception {
		int recSize;
		RID rid = null;
		Scan pscan = null;

		rid = new RID();

		// OPEN SCAN

		pscan = new Scan(this,tx);

		// SCAN FILE

		while (true) {
			tuple = pscan.getNext(rid,tx,relation);
			if (tuple == null)
				return false;
			initTuple();
			readTuple(tuple, record,tx);

			if (record.IndexName.equalsIgnoreCase(IndexName)==true)
				break; // FOUND
		}

		pscan.closeScan();

		return true;
	}

	// GET ALL INDEXES INLUDING A SPECIFIED ATTRIBUTE
	public IndexSchema[] getAttrIndexes(String relation, String attrName,
			int indexCnt,Transaction tx) throws Exception {
		attrSchema record = null;
		int status;
		int recSize;
		RID rid = null;
		Scan pscan = null;
		int count = 0;

		rid = new RID();
		record = new attrSchema();

		record = GoggleDB.attrMgr.getInfo(relation, attrName,tx);

		// ASSIGN INDEX COUNT

		indexCnt = record.indexCnt;
		if (indexCnt == 0)
			return null;

		// OPEN SCAN

		pscan = new Scan(this,tx);

		// ALLOCATE INDEX ARRAY

		IndexSchema[] indexes = new IndexSchema[indexCnt];
		if (indexes == null) {
			pscan.closeScan();

			throw new DBException(null, "Catalog: No Enough Memory!");
		}

		// SCAN FILE

		while (true) {

			tuple = pscan.getNext(rid,tx,relation);
			if (tuple == null)
				throw new DBException(null, "Catalog: Index not Found!");
			initTuple();
			readTuple(tuple, indexes[count],tx);

			if (indexes[count].relName.equalsIgnoreCase(relation) == true
					&& indexes[count].attrName.equalsIgnoreCase(attrName) == true)
				count++;

			if (count == indexCnt) // if all indexes found
				break;
		}

		pscan.closeScan();
		return indexes;
	}

	// CREATES A FILE NAME FOR AN INDEX
	/*public String buildIndexName(String relation, String attrName,
			IndexType accessType) {
		String accessName = null;
		int sizeName;
		int sizeOfByte = 1;
		String indexName = null;

		// DETERMINE INDEX TYPE

		if (accessType.indexType == IndexType.B_Index)
			accessName = new String("B_Index");
		else if (accessType.indexType == IndexType.Hash)
			accessName = new String("Hash");

		// CHECK FOR LEGIT NAME SIZE

		sizeName = relation.length() + accessName.length() + attrName.length()
				+ (3 * sizeOfByte);

		// if(sizeName > MAXNAME)
		// return MINIBASE_FIRST_ERROR( CATALOG, Catalog::INDEX_NAME_TOO_LONG );

		// CREATE NAME

		indexName = new String(relation);
		indexName = indexName.concat("-");
		indexName = indexName.concat(accessName);
		indexName = indexName.concat("-");
		indexName = indexName.concat(attrName);

		return indexName;
	}*/

	// ADD INDEX ENTRY TO CATALOG
	public void addInfo(IndexSchema record,Transaction tx) throws Exception {
		RID rid;

		rid = new RID();
		
		createTuple();
		makeTuple(tuple, record);
		insertRecord(tuple.getTupleByteArray(tx),tx,fileName);

	}

	// REMOVE INDEX ENTRY FROM CATALOG
	public void removeInfo(String tableName, String attrName,String indexName,
			IndexType accessType,Transaction tx) throws Exception {
		int recSize;
		RID rid = null;
		Scan pscan = null;
		IndexSchema record = null;

		rid = new RID();
		record = new IndexSchema();

		// OPEN SCAN
		pscan = new Scan(this,tx);
		
		// SCAN FILE

		while (true) {
			
			tuple = pscan.getNext(rid,tx,tableName);
			if (tuple == null)
				throw new DBException(null, "Catalog: Attribute not Found!");
			initTuple();
			readTuple(tuple, record,tx);

			if (record.relName.equalsIgnoreCase(tableName) == true
					&& record.attrName.equalsIgnoreCase(attrName) == true
					&& (record.accessType.indexType == accessType.indexType)&&record.IndexName.equalsIgnoreCase(indexName)) {

				deleteRecord(rid,tx,tableName); // FOUND - DELETE

				break;
			}
		}

		pscan.closeScan();

		return;
	};

	// ADD INDEX TO A RELATION
	public void addIndex(String tableName, String attrName,String indexName,
			IndexType accessType, Transaction tx) throws Exception {
		RID rid = null;
		IndexSchema indexRec = null;
		attrSchema attrRec = null;
		int intKey = 0;
		float floatKey = (float) 0.0;
		String charKey = null;
		int attrCnt = 0;
		KeyClass key = null;
		int recSize = 0;

		Heapfile datafile = null;
		
		Tuple tuple = null;
		BTFile btree = null;
		Scan pscan = null;
		Types[] typeArray = null;
		short[] sizeArray = null;

		rid = new RID();
		indexRec = new IndexSchema();
		attrRec = new attrSchema();
		// CHECK FOR EXISTING INDEX

		if (getInfo(tableName, indexName, indexRec,tx)) {
			throw new DBException(null, "Index has Exist");
		}

		// GET ATTRIBUTE INFO

		attrRec = GoggleDB.attrMgr.getInfo(tableName, attrName,tx);

		
		// UPDATE ATTRIBUTE INFO

		attrRec.indexCnt++;

		GoggleDB.attrMgr.removeInfo(tableName, attrName,tx);
		GoggleDB.attrMgr.addInfo(attrRec,tx);

		// BUILD INDEX FILE NAME
		//indexName = buildIndexName(tableName, attrName, accessType);

		// IF BTREE
		if (accessType.indexType == IndexType.B_Index) {
			btree = new BTFile(indexName, attrRec.attrType.attrType,
					attrRec.attrLen, 0, tx);
		}

		// ADD ENTRY IN INDEXCAT
		indexRec.relName = new String(tableName);
		indexRec.attrName = new String(attrName);
		indexRec.accessType = accessType;

		if (accessType.indexType == IndexType.B_Index) {
			indexRec.order = new TupleOrder(TupleOrder.Ascending);
		} else {
			indexRec.order = new TupleOrder(TupleOrder.Random);
		}
		indexRec.distinctKeys = DISTINCTKEYS;
		indexRec.clustered = 0; // 0 means non-clustered!!!!

		indexRec.indexPages = INDEXPAGES;
		indexRec.IndexName = indexName;

		addInfo(indexRec,tx);

		// PREPARE TO SCAN DATA FILE

		datafile = new Heapfile(tableName, tx);

		pscan = datafile.openScan(tx);

		// PREPARE TUPLE

		Types[][] typeArrayEM = new Types[1][];
		short[][] sizeArrayEM = new short[1][];

		attrCnt = GoggleDB.attrMgr.getTupleStructureEm(tableName, typeArrayEM,
				sizeArrayEM,tx);
		typeArray = typeArrayEM[0];
		sizeArray = sizeArrayEM[0];

		tuple = new Tuple(Tuple.maxSize);
		if (tuple == null) {
			pscan.closeScan();
			throw new DBException(null, "Catalog, No Enough Memory!");
		}

		tuple.setHdr((short) attrCnt, typeArray, sizeArray);

		recSize = tuple.size();

		// NOW PROCESS THE HEAPFILE AND INSERT KEY,RID INTO INDEX

		while (true) {
			tuple = pscan.getNext(rid,tx,tableName);
			if (tuple == null)
				break;
			tuple.setHdr((short) attrCnt, typeArray, sizeArray);

			// PULL OUT THE KEY VALUE FROM HEAPFILE RECORD

			if (attrRec.attrType.attrType == Types.INT||attrRec.attrType.attrType == Types.BOOLEAN) {
				intKey = tuple.getIntFld(attrRec.attrPos,tx,tableName);
				key = new IntegerKey(intKey);
			} else if (attrRec.attrType.attrType == Types.FLOAT) {
				floatKey = tuple.getFloFld(attrRec.attrPos,tx,tableName);
				key = new FloatKey(new Float( floatKey));
			} else if (attrRec.attrType.attrType == Types.CHAR|| attrRec.attrType.attrType==Types.VARCHAR||attrRec.attrType.attrType==Types.DATETIME||attrRec.attrType.attrType==Types.TIMESTAMP || attrRec.attrType.attrType==Types.DECIMAL) {
				charKey = new String(tuple.getStrFld(attrRec.attrPos,tx,tableName));
				key = new StringKey(charKey);
			}

			// NOW INSERT RECORD INTO INDEX

			if (accessType.indexType == IndexType.B_Index) {
				btree.insert(key, rid,tx,indexName);

			}
		}

		pscan.closeScan();

	}

	// DROP INDEX FROM A RELATION
	void dropIndex(String relation,String indexName, IndexType accessType,
			Transaction tx) throws Exception {
		IndexSchema indexRec = null;
		attrSchema attrRec = null;

		indexRec = new IndexSchema();
		attrRec = new attrSchema();

		getInfo(relation, indexName, indexRec,tx);

		attrRec = GoggleDB.attrMgr.getInfo(relation, indexRec.attrName,tx);

		attrRec.indexCnt--;

		GoggleDB.attrMgr.removeInfo(relation, indexRec.attrName,tx);
		GoggleDB.attrMgr.addInfo(attrRec,tx);

		if (accessType.indexType == IndexType.B_Index) {
			BTFile btree;
			btree = new BTFile(indexName, attrRec.attrType.attrType,
					attrRec.attrLen, 0, tx);

			btree.destroyFile(tx,indexName);

		}

		removeInfo(relation, indexRec.attrName,indexName, accessType,tx);

	}

	// DROP ALL INDEXES FOR A RELATION
	void dropRelation(String relation) {
	};

	void makeTuple(Tuple tuple, IndexSchema record) throws Exception {

		tuple.SsetStrFld(1, record.relName);
		tuple.SsetStrFld(2, record.attrName);

		if (record.accessType.indexType == IndexType.None)
			tuple.SsetIntFld(3, 0);
		else if (record.accessType.indexType == IndexType.B_Index)
			tuple.SsetIntFld(3, 1);
		else if (record.accessType.indexType == IndexType.Hash)
			tuple.SsetIntFld(3, 2);
		else
			System.out.println("Invalid accessType in IndexCatalog::make_tupl");

		if (record.order.tupleOrder == TupleOrder.Ascending)
			tuple.SsetIntFld(4, 0);
		else if (record.order.tupleOrder == TupleOrder.Descending)
			tuple.SsetIntFld(4, 1);
		else if (record.order.tupleOrder == TupleOrder.Random)
			tuple.SsetIntFld(4, 2);
		else
			System.out.println("Invalid order in IndexCatalog::make_tuple");

		tuple.SsetIntFld(5, record.clustered);
		tuple.SsetIntFld(6, record.distinctKeys);
		tuple.SsetIntFld(7, record.indexPages);
		//System.out.println("haha"+tuple.SsetStrFld(8, "haha"));
		tuple.SsetStrFld(8, record.IndexName);

		return;
	};

	void readTuple(Tuple tuple, IndexSchema record,Transaction tx) throws Exception {

		record.relName = tuple.getStrFld(1,tx,fileName);
		record.attrName = tuple.getStrFld(2,tx,fileName);

		int temp;
		temp = tuple.getIntFld(3,tx,fileName);
		if (temp == 0)
			record.accessType = new IndexType(IndexType.None);
		else if (temp == 1)
			record.accessType = new IndexType(IndexType.B_Index);
		else if (temp == 2)
			record.accessType = new IndexType(IndexType.Hash);
		else
			System.out.println("111Error in IndexCatalog::read_tuple");

		temp = tuple.getIntFld(4,tx,fileName);
		if (temp == 0)
			record.order = new TupleOrder(TupleOrder.Ascending);
		else if (temp == 1)
			record.order = new TupleOrder(TupleOrder.Descending);
		else if (temp == 2)
			record.order = new TupleOrder(TupleOrder.Random);
		else
			System.out.println("222Error in IndexCatalog::read_tuple");

		record.clustered = tuple.getIntFld(5,tx,fileName);
		record.distinctKeys = tuple.getIntFld(6,tx,fileName);
		record.indexPages = tuple.getIntFld(7,tx,fileName);
		record.IndexName = tuple.getStrFld(8, tx, fileName);

		return;

	}

	Tuple tuple;

	short[] strSizes;

	Types[] attrs;

}
